package qc.gateway.filter;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.StringUtils;
import org.reactivestreams.Publisher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import qc.common.core.constants.QCAuthConstant;
import qc.common.core.constants.QCConstant;
import qc.common.core.enums.ApiLogLevelEnum;
import qc.common.core.enums.QCAuthValidateResultEnum;
import qc.common.core.enums.ResourceStatusFlagEnum;
import qc.common.core.http.Response;
import qc.common.core.http.ResponseStatusEnum;
import qc.common.core.utils.DateUtil;
import qc.common.core.utils.JsonUtil;
import qc.gateway.dto.QcApiLogDto;
import qc.gateway.entity.*;
import qc.gateway.service.ApiAuthorityService;
import qc.gateway.service.QcApiFilterService;
import qc.gateway.service.QcApiLogService;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;

/**
 * QCGatewayFilter 渠成Gateway全局过滤器
 * 统一完成以下功能：
 * 1.对每个请求的request、response进行捕获，可用作请求处理日志记录
 * 2.对请求中出现的异常进行捕获并封装处理
 * 3.对请求响应的最终返回内容进行封装，api中只需要返回数据或结果，在gateway中全局添加code、msg
 *
 * @author QuCheng Tech
 */
@Component
@RefreshScope
public class QcGlobalFilter implements GlobalFilter, Ordered {
    private QcApiLogService qcApiLogService;
    private QcApiFilterService qcApiFilterService;
    private ApiAuthorityService apiAuthorityService;

    @Autowired
    public void setQCGlobalLogService(QcApiLogService qcApiLogService) {
        this.qcApiLogService = qcApiLogService;
    }

    @Autowired
    public void setQcApiFilterService(QcApiFilterService qcApiFilterService) {
        this.qcApiFilterService = qcApiFilterService;
    }

    @Autowired
    public void setApiAuthorityService(ApiAuthorityService apiAuthorityService) {
        this.apiAuthorityService = apiAuthorityService;
    }

    /**
     * 排除过滤的uri地址，包含不需要权限验证的，如登录接口、swagger等
     */
    @Value("#{'${qucheng.gateway.white-list:auth/login v2/api-docs}'.split(' ')}")
    private String[] whiteList;

    /**
     * Api日志记录等级，可以从nacos配置读取，默认为Stable
     * 在日志记录等级为DEV时同时在控制台输出调试信息
     * 2023-11-6，实测需要加@RefreshScope注解才能自动刷新（否则在nacos中修改后会看见有更新但实际未生效）；在nacos中配置枚举需要使用name（不区分大小写），不能使用index
     */
    @Value("#{'${qucheng.gateway.log-level:ApiLogLevelEnum.Stable}'}")
    private ApiLogLevelEnum API_LOG_LEVEL;

    /***
     * 获取过滤规则的间隔分钟数，从nacos配置读取，默认为5分钟
     */
    @Value("#{'${qucheng.gateway.refresh-minutes:5}'}")
    private long REFRESH_FILTERS_MINUTES;

    /***
     * 响应数据记录日志的最大长度，如果响应数据超过该长度不在日志中进行记录和显示，从nacos配置读取，默认为1000
     */
    @Value("#{'${qucheng.gateway.max-data-length:1000}'}")
    private int WRITE_LOG_MAX_DATA_LENGTH;

    /***
     * 上次获取过滤规则的时间
     */
    private Date latestGetFiltersDate = null;

    /**
     * API日志过滤规则
     */
    private List<QcApiFilters> apiFilters;
    /**
     * 网关中favicon，不进行API接口权限验证
     */
    private static final String Url_Favicon = "/favicon.ico";

    /**
     * 网关中刷新api接口权限验证数据URL，在匹配到请求的url为刷新API接口权限验证数据时从数据库重新提取基础信息
     */
    private static final String Url_GetApiAuthorityDatas = "/qc/gateway/api-refresh";

    /**
     * 是否需要处理API接口权限，为false时不验证权限，均允许访问
     */
    //private boolean NeedHandleApiAuthority = false;
    private boolean NeedHandleApiAuthority = true;

    /**
     * 处理API接口权限所需要的角色信息集合
     */
    private List<QcRoles> ApiAuthorityRoles;

    /**
     * 处理API接口权限所需要的部门信息集合
     */
    private List<QcDepts> ApiAuthorityDepts;

    /**
     * 处理API接口权限所需要的部门与用户关联关系
     */
    private List<QcDeptUser> ApiAuthorityDeptUsers;

    /**
     * 处理API接口权限所需要的菜单信息集合
     */
    private List<QcMenus> ApiAuthorityMenus;

    /**
     * 处理API接口权限所需要的API接口信息集合
     */
    private List<QcApis> ApiAuthorityApis;

    /**
     * 处理API接口权限所需要的菜单与API接口关联关系
     */
    private List<QcMenuApi> ApiAuthorityMenuApis;

    /**
     * 处理API接口权限所需要的角色与用户关联关系
     */
    private List<QcRoleUser> ApiAuthorityRoleUsers;

    /**
     * 处理API接口权限所需要的角色与部门关联关系
     */
    private List<QcRoleDept> ApiAuthorityRoleDepts;

    /**
     * 处理API接口权限所需要的角色与菜单关联关系
     */
    private List<QcRoleMenu> ApiAuthorityRoleMenus;

    /**
     * 处理API接口权限所需要的角色与Api关联关系
     */
    private List<QcRoleApi> ApiAuthorityRoleApis;

    /**
     * 处理API接口权限所需要的App信息集合
     */
    private List<QcApps> ApiAuthorityApps;

    /**
     * 处理API接口权限所需要的App与Key关联关系
     */
    private List<QcAppKey> ApiAuthorityAppKeys;

    /**
     * url路径匹配器
     */
    private AntPathMatcher pathMatcher = new AntPathMatcher();

    /**
     * 渠成后台API接口网关统一过滤器处理，包含获取request、response，身份验证和权限控制处理
     *
     * @param exchange 服务网络交换器，包含请求和响应，请求和响应Context
     * @param chain    网关过滤链表
     * @return reactor.core.publisher.Mono<java.lang.Void>
     * @author QuCheng Tech
     * @since 2023/6/4
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //获取请求和相应
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();

        //定义一个对象用于记录request和response信息
        QcApiLogDto apiLog = new QcApiLogDto();
        //自动生成一个唯一的requestId和请求的进入时间（用于后面计算本次请求响应总耗时）
        String requestId = UUID.randomUUID().toString();
        apiLog.setRequestId(requestId);
        apiLog.setStartTime(System.currentTimeMillis());

        //获取请求信息
        //后继需完善权限验证，根据token和访问的url进行判断处理
        //参考：
        //spring boot使用自定义过滤器实现接口认证:https://blog.csdn.net/qq_25391785/article/details/128114558
        //请求 http 头
        HttpHeaders requestHeaders = request.getHeaders();
        //请求头中的信息
        MediaType requestHeaderContentType = requestHeaders.getContentType();
        apiLog.setRequestHeaderContentType(requestHeaderContentType);
        InetSocketAddress requestHeaderUserAgent = requestHeaders.getHost();
        //requestHeaders.getHost和request.getRemoteAddress的差别？？是否能获取到实际访问客户端的地址信息
        InetSocketAddress requestRemoteAddress = request.getRemoteAddress();
        apiLog.setRequestHeaderUserAgent(requestHeaderUserAgent);
        apiLog.setRequestRemoteAddress(requestRemoteAddress);
        // 获取请求的 token
        String token = requestHeaders.getFirst(QCAuthConstant.TOKEN_HEADER_KEY);
        apiLog.setToken(token);
        //请求 uri
        String requestUri = request.getURI().toString();
        apiLog.setRequestUri(requestUri);
        //获取请求的url
        String requestUrl = request.getPath().value();
        apiLog.setRequestUrl(requestUrl);
        //请求 http 方法
        HttpMethod requestMethod = request.getMethod();
        apiLog.setRequestMethod(requestMethod);
        //请求中的QueryParams
        MultiValueMap<String, String> requestQueryParams = request.getQueryParams();
        apiLog.setRequestQueryParams(requestQueryParams);

        //当前时间，方便后面使用
        Date now = DateUtil.getNowDate();

        DataBufferFactory dataBufferFactory = response.bufferFactory();

        if (API_LOG_LEVEL == ApiLogLevelEnum.DEV) {
            System.out.println("API_LOG_LEVEL.getIndex()=" + API_LOG_LEVEL.getIndex() + " API_LOG_LEVEL.getName()=" + API_LOG_LEVEL.getName());
        }

        //访问Url_Favicon，直接返回成功
        if (pathMatcher.match(Url_Favicon, requestUrl)) {
            return responseByStatus(exchange, ResponseStatusEnum.SUCCESS, null, apiLog);
        }
        //匹配请求url是否为刷新API接口权限验证使用的基础数据
        if (pathMatcher.match(Url_GetApiAuthorityDatas, requestUrl)) {
            try {
                GetApiAuthorityDatas();
                return responseByStatus(exchange, ResponseStatusEnum.SUCCESS, "获取API接口访问权限控制所需数据成功。", apiLog);
            } catch (Exception ex) {
                return responseByStatus(exchange, ResponseStatusEnum.ERROR, "获取API接口访问权限控制所需数据异常，" + ex.getMessage(), apiLog);
            }
        }

        //判断是否需要验证API接口权限
        if (NeedHandleApiAuthority == true) {
            //Spring中的路径匹配器AntPathMatcher详解:https://www.jb51.net/program/299235mbq.htm
            //Spring工具类–路径匹配(AntPathMatcher)的使用:https://learn.skyofit.com/archives/2265
            //判断后最终得到访问的API接口是否有权限
            boolean hasApiAuthority = false;
            //先使用白名单进行匹配，白名单中的不需要进行验证判断处理
            if (whiteList != null && whiteList.length > 0x0) {
                for (String pattern : whiteList) {
                    boolean hasMatched = pathMatcher.match(pattern, requestUrl);
                    if (API_LOG_LEVEL == ApiLogLevelEnum.DEV)
                        System.out.println("验证Api访问权限，白名单匹配结果 hasMatched=" + hasMatched + " pattern=" + pattern + " requestUrl=" + requestUrl);
                    if (hasMatched) {
                        //访问的url在白名单中，有访问权限
                        hasApiAuthority = true;
                        break;
                    }
                }
            }
            //测试用：匹配到对应的路由url，允许继续访问
            //if (pathMatcher.match("/qzj/test/1", requestUrl)) {
            //    hasApiAuthority = true;
            //}
            if (API_LOG_LEVEL == ApiLogLevelEnum.DEV)
                System.out.println("验证Api访问权限，白名单匹配结果hasApiAuthority=" + hasApiAuthority + " requestUrl=" + requestUrl);
            //如果系统中没有如下配置，无法判断API接口权限，在此情况下均允许访问
            if (ApiAuthorityApis == null || ApiAuthorityApis.size() < 0x1)
                //没有任何API接口信息
                hasApiAuthority = true;
            else if (ApiAuthorityRoles == null || ApiAuthorityRoles.size() < 0x1)
                //没有任何角色信息
                hasApiAuthority = true;
            else if (ApiAuthorityRoleApis == null || ApiAuthorityRoleApis.size() < 0x1)
                //没有任何角色与API接口关联信息
                hasApiAuthority = true;

            if (API_LOG_LEVEL == ApiLogLevelEnum.DEV)
                System.out.println("验证Api访问权限，API接口权限基础数据配置判断结果hasApiAuthority=" + hasApiAuthority + " requestUrl=" + requestUrl);

            //如果白名单中判断没有权限，获取token进行角色的api访问权限判断
            if (hasApiAuthority == false) {
                //先判断是否有token
                if (StringUtils.isBlank(token))
                    //没有token，直接返回没有token结果
                    return responseByStatus(exchange, ResponseStatusEnum.AUTH_NO_TOKEN, null, apiLog);
                //有token，判断token是否有效
                //从登录日志中根据token获取登录日志状态
                QcUserloginLogs userloginLog = apiAuthorityService.getUserLoginLogByToken(token);
                if (userloginLog == null)
                    //token无效，根据token获取不到登录记录
                    return responseByStatus(exchange, ResponseStatusEnum.AUTH_INVALID_TOKEN, null, apiLog);
                //判断登录记录中的状态
                //先判断登录结果和UID，再判断token是否过期
                if (userloginLog.getStatus() != QCAuthValidateResultEnum.SUCCESS)
                    //登录的结果不是成功，返回未授权访问
                    return responseByStatus(exchange, ResponseStatusEnum.AUTH_AUTHENTICATION_FAILED, null, apiLog);
                //获取登录记录中的用户ID，是否需要判断？如果是通过key进行登录的是否有用户ID
                //判断登录状态的过期时间，如果有过期时间判断是否过期
                if (userloginLog.getExpiredtm() != null && userloginLog.getExpiredtm().before(now))
                    //有过期时间并且过期时间在当前时间之前，返回令牌已过期
                    return responseByStatus(exchange, ResponseStatusEnum.AUTH_EXPIRED_TOKEN, null, apiLog);

                //到期token的有效性已经判断完毕，需要判断是否有对应的API接口权限
                //需要获取token对应的用户ID或AppKey，并获取当前的项目编码（后继的获取角色、菜单、API接口权限都需要在当前项目中获取）
                boolean isAppKey = false;
                //判断是用户还是AppKey的方法：如果登录记录中的UID有值说明是用户，否则说明是AppKey--获取UCODE作为对应的AppKey
                int userId = 0x0;
                String appKey = null;
                //项目编码的获取：如果是用户登录，从请求头中获取；如果是AppKey从登录记录中获取（一个AppKey只对应一个确定的项目）
                String projectCode = null;
                if (userloginLog.getUid() != null && userloginLog.getUid().intValue() > 0x0) {
                    userId = userloginLog.getUid().intValue();
                } else {
                    isAppKey = true;
                    appKey = userloginLog.getUcode();
                    projectCode = userloginLog.getProject();
                }

                /**
                 * API接口访问权限判断处理流程：
                 * 一、根据用户ID或者AppKey获取角色ID集合
                 *      1.访问来源为用户登录，根据用户ID判断
                 *          用户ID--角色关联用户、角色关联部门+部门与用户关联--并集得到角色ID集合
                 *      2.访问来源为AppKey，根据AppKey判断
                 *          AppKey--AppKey找到对应的App（一个App可以分配多个Keys—）--得到角色ID（App中有权限角色属性，对应1个角色）
                 * 二、根据角色ID集合获取API集合
                 *      角色ID集合--逐个角色ID获取角色关联的API唯一标识集合（并集得到API唯一标识）--API唯一标识集合获取API信息（判断状态）
                 * 三、使用AntPathMatcher在API信息集合中进行url匹配
                 *
                 * 2024-4-24，可以考虑：优化处理流程如下：--未实现
                 * 对关联的角色逐个进行判断是否有权限，如果当前判断角色已有访问权限就不需要将所有关联角色均判断一遍
                 * 还需要处理角色关联的菜单关联的API接口，API接口权限规则中默认为：有菜单权限的，菜单关联的API接口均有访问权限
                 */
                //得到的有权限的角色ID
                List<Integer> hasAuthorityRoleIds = new ArrayList<>();
                //步骤一、根据用户ID或者AppKey获取角色ID集合
                //至少要求角色集合、API接口集合、角色关联API接口集合不为空才能进行判断处理
                if (ApiAuthorityRoles != null && ApiAuthorityRoles.size() > 0x0 && ApiAuthorityApis != null && ApiAuthorityApis.size() > 0x0 && ApiAuthorityRoleApis != null && ApiAuthorityRoleApis.size() > 0x0) {
                    if (isAppKey == true) {
                        //访问来源为通过AppKey授权调用
                        //AppKey不能为空
                        if (StringUtils.isBlank(appKey)) {
                            //提示，AppKey不能为空
                        } else {
                            /**
                             * 1.先根据AppKey获取信息，判断AppKey信息的启用状态和有效时间范围
                             * 2.根据AppKey获取App信息，判断App信息中启用状态和角色ID属性
                             */
                        }
                        if (ApiAuthorityApps != null && ApiAuthorityApps.size() > 0x0 && ApiAuthorityAppKeys != null && ApiAuthorityAppKeys.size() > 0x0) {
                            String finalAppKey = appKey;
                            List<QcAppKey> appKeys = ApiAuthorityAppKeys.stream().filter(p -> StringUtils.equalsIgnoreCase(p.getKey(), finalAppKey) && p.getEnable() == true).collect(Collectors.toList());
                            if (appKeys != null && appKeys.size() > 0x0) {
                                //判断有效时间范围
                                QcAppKey firstAppKey = appKeys.get(0x0);
                                if (firstAppKey.getBegintm().before(now) && (firstAppKey.getEndtm() == null || firstAppKey.getEndtm().after(now))) {
                                    String appId = firstAppKey.getAppid();
                                    //根据AppId获取App信息
                                    List<QcApps> apps = ApiAuthorityApps.stream().filter(p -> StringUtils.equalsIgnoreCase(p.getId(), appId) && p.getEnable() == true).collect(Collectors.toList());
                                    if (apps != null && apps.size() > 0x0) {
                                        QcApps firstApp = apps.get(0x0);
                                        //判断是否有关联角色，角色ID不能为空，并且大于0
                                        if (firstApp.getRoleid() != null && firstApp.getRoleid().intValue() > 0x0) {
                                            hasAuthorityRoleIds.add(firstApp.getRoleid());
                                        }
                                    }
                                }
                            }
                        }
                    } else {
                        //访问来源为用户登录
                        //可能角色有用户关联或者角色由用户所在部门关联，因此不用判断用户集合和角色关联用户集合是否为空
                        //判断项目为空时无法在指定的项目中获取角色
                        if (StringUtils.isBlank(projectCode)) {
                            //提示，项目编码不能为空
                        } else {
                            /**
                             * 1.先根据项目编码获取项目中的角色ID集合、部门ID集合
                             * 2.根据用户关联部门ID（在项目所有部门ID集合）获取部门关联角色ID集合
                             * 3.用户关联的角色ID集合与部门关联的角色ID集合取并集
                             */
                            int finalUserId = userId;
                            String finalProjectCode = projectCode;
                            List<Integer> projectAllValidRoleIds = ApiAuthorityRoles.stream().filter(p -> StringUtils.equalsIgnoreCase(p.getProjectcode(), finalProjectCode) && p.getFlag() == ResourceStatusFlagEnum.NORMAL).map(p -> p.getId()).collect(Collectors.toList());
                            if (projectAllValidRoleIds != null && projectAllValidRoleIds.size() > 0x0) {
                                //记录项目中用户直接关联的角色ID集合
                                List<Integer> userRoleIds = new ArrayList<>();
                                if (ApiAuthorityRoleUsers != null && ApiAuthorityRoleUsers.size() > 0x0) {
                                    //获取用户关联的角色ID集合：用户与角色关联中角色在当前项目中、用户是当前用户
                                    userRoleIds = ApiAuthorityRoleUsers.stream().filter(p -> projectAllValidRoleIds.contains(p.getRoleid()) && p.getUserid() == finalUserId).map(p -> p.getRoleid()).collect(Collectors.toList());
                                }
                                //记录项目中用户通过关联部门得到的角色ID集合
                                List<Integer> userDeptRoleIds = new ArrayList<>();
                                if (ApiAuthorityDepts != null && ApiAuthorityDepts.size() > 0x0 && ApiAuthorityRoleDepts != null && ApiAuthorityRoleDepts.size() > 0x0) {
                                    //获取项目中的所有有效部门ID集合
                                    List<Integer> projectAllValidDeptIds = ApiAuthorityDepts.stream().filter(p -> StringUtils.equalsIgnoreCase(p.getProjectcode(), finalProjectCode) && p.getFlag() == ResourceStatusFlagEnum.NORMAL).map(p -> p.getId()).collect(Collectors.toList());
                                    if (projectAllValidDeptIds != null && projectAllValidDeptIds.size() > 0x0) {
                                        if (ApiAuthorityDeptUsers != null && ApiAuthorityDeptUsers.size() > 0x0) {
                                            //获取用户关联的部门ID集合：部门关联用户中的用户为当前用户ID、部门关联用户中的部门ID在当前项目的所有有效部门集合中
                                            List<Integer> userDeptIds = ApiAuthorityDeptUsers.stream().filter(p -> projectAllValidDeptIds.contains(p.getDeptid()) && p.getUserid() == finalUserId).map(p -> p.getDeptid()).collect(Collectors.toList());
                                            if (userDeptIds != null && userDeptIds.size() > 0x0) {
                                                //获取部门ID集合关联的角色ID集合：角色与部门关联中角色在当前项目中、部门ID用户关联的部门ID集合中
                                                userDeptRoleIds = ApiAuthorityRoleDepts.stream().filter(p -> projectAllValidRoleIds.contains(p.getRoleid()) && userDeptIds.contains(p.getDeptid())).map(p -> p.getDeptid()).collect(Collectors.toList());
                                            }
                                        }
                                    }
                                }
                                //循环当前项目中的所有角色，如果角色ID在用户关联的角色ID集合中或在用户关联部门的角色ID集合中，该用户均有该角色
                                for (Integer roleId : projectAllValidRoleIds) {
                                    if (hasAuthorityRoleIds.contains(roleId) == false) {
                                        //已存在的不需要重复添加
                                        if (userRoleIds.size() > 0x0 && userRoleIds.contains(roleId))
                                            hasAuthorityRoleIds.add(roleId);
                                        else if (userDeptRoleIds.size() > 0x0 && userDeptRoleIds.contains(roleId))
                                            hasAuthorityRoleIds.add(roleId);
                                    }
                                }
                            }
                        }
                    }
                }
                if (API_LOG_LEVEL == ApiLogLevelEnum.DEV)
                    System.out.println("根据token验证Api访问权限，isAppKey=" + isAppKey + " userId=" + userId + " appKey=" + appKey + " projectCode=" + projectCode + " 有权限的角色hasAuthorityRoleIds=" + hasAuthorityRoleIds.size());
                //步骤二、根据角色ID集合获取API集合，获取角色关联API和角色关联菜单--关联API的数据并集
                if (hasAuthorityRoleIds != null && hasAuthorityRoleIds.size() > 0x0) {
                    if (ApiAuthorityApis != null && ApiAuthorityApis.size() > 0x0) {
                        //步骤2-1、获取角色直接关联的API集合
                        //步骤2-2、获取角色关联的菜单集合，再根据菜单集合获取关联的API集合
                        //在此遍历每个角色进行验证API接口权限，如果有就跳出，不继续判断下一个角色
                        if (ApiAuthorityRoleApis != null && ApiAuthorityRoleApis.size() > 0x0) {
                            List<String> hasAuthorityApiIds = ApiAuthorityRoleApis.stream().filter(p -> hasAuthorityRoleIds.contains(p.getRoleid())).map(p -> p.getApi()).collect(Collectors.toList());
                            if (hasAuthorityApiIds != null && hasAuthorityApiIds.size() > 0x0) {
                                //根据获取到的有访问权限的API接口唯一标识获取接口信息进行url匹配
                                List<QcApis> hasAuthorityApis = ApiAuthorityApis.stream().filter(p -> hasAuthorityApiIds.contains(p.getId())).collect(Collectors.toList());
                                if (hasAuthorityApis != null && hasAuthorityApis.size() > 0x0) {
                                    //步骤三、使用AntPathMatcher在API信息集合中进行url匹配
                                    for (QcApis api : hasAuthorityApis) {
                                        if (pathMatcher.match(api.getUrl(), requestUrl)) {
                                            //如果要判断请求方式，可以在此增加判断；
                                            //if (StringUtils.equalsIgnoreCase(api.getMethod(), requestMethod.name())) {
                                            //
                                            //}
                                            //如果是AppKey访问可以记录请求信息
                                            if (isAppKey == true) {
                                                //appKey就是对应的授权访问Key，根据key修改最新使用时间、调用接口次数+1
                                            }
                                            //匹配到，有权限
                                            hasApiAuthority = true;
                                            break;
                                        }
                                    }
                                }
                            }
                        }
                        //如果上面的根据角色直接关联的API接口集合全部判断完均没有访问权限，根据角色关联菜单进行判断；要求必须为用户登录，在当前项目中获取菜单进行判断
                        if (hasApiAuthority == false && isAppKey == false && StringUtils.isNotBlank(projectCode)) {
                            if (ApiAuthorityMenus != null && ApiAuthorityMenus.size() > 0x0 && ApiAuthorityRoleMenus != null && ApiAuthorityRoleMenus.size() > 0x0 && ApiAuthorityMenuApis != null && ApiAuthorityMenuApis.size() > 0x0) {
                                //获取项目中的所有有效菜单集合
                                String finalProjectCode1 = projectCode;
                                List<Integer> projectAllValidMenuIds = ApiAuthorityMenus.stream().filter(p -> StringUtils.equalsIgnoreCase(p.getProjectcode(), finalProjectCode1) && p.getFlag() == ResourceStatusFlagEnum.NORMAL).map(p -> p.getId()).collect(Collectors.toList());
                                if (projectAllValidMenuIds != null && projectAllValidMenuIds.size() > 0x0) {
                                    //根据角色与菜单关联关系在此过滤：当前项目菜单集合包含菜单ID、有权限的角色集合包含角色ID
                                    List<Integer> hasAuthorityMenuIds = ApiAuthorityRoleMenus.stream().filter(p -> projectAllValidMenuIds.contains(p.getMenuid()) && hasAuthorityRoleIds.contains(p.getRoleid())).map(p -> p.getMenuid()).collect(Collectors.toList());
                                    if (hasAuthorityMenuIds != null && hasAuthorityMenuIds.size() > 0x0) {
                                        List<String> hasAuthorityApiIds = ApiAuthorityMenuApis.stream().filter(p -> hasAuthorityMenuIds.contains(p.getId())).map(p -> p.getApi()).collect(Collectors.toList());
                                        if (hasAuthorityApiIds != null && hasAuthorityApiIds.size() > 0x0) {
                                            //根据获取到的有访问权限的API接口唯一标识获取接口信息进行url匹配
                                            List<QcApis> hasAuthorityApis = ApiAuthorityApis.stream().filter(p -> hasAuthorityApiIds.contains(p.getId())).collect(Collectors.toList());
                                            if (hasAuthorityApis != null && hasAuthorityApis.size() > 0x0) {
                                                //步骤三、使用AntPathMatcher在API信息集合中进行url匹配
                                                for (QcApis api : hasAuthorityApis) {
                                                    if (pathMatcher.match(api.getUrl(), requestUrl)) {
                                                        //如果要判断请求方式，可以在此增加判断；
                                                        //if (StringUtils.equalsIgnoreCase(api.getMethod(), requestMethod.name())) {
                                                        //
                                                        //}
                                                        //匹配到，有权限
                                                        hasApiAuthority = true;
                                                        break;
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }

                //没有对应API接口权限，返回权限不足
                if (hasApiAuthority == false)
                    return responseByStatus(exchange, ResponseStatusEnum.AUTH_PERMISSION_DENIED, null, apiLog);
            }
        }

        //链路追踪一般采用sleuth和zipkin进行结合首先引入依赖
        //3 判断是否是有效的token
        //OAuth2AccessToken oAuth2AccessToken;
        //try {
        //    oAuth2AccessToken = null;//tokenStore.readAccessToken(token);
        //
        //    if(oAuth2AccessToken.isExpired()){
        //        //token过期处理
        //        return tokenExpiredHandle(exchange);
        //    }
        //    else
        //    {
        //        //将用户信息传递给微服务，暂时注释
        //        //Map<String, Object> additionalInformation = oAuth2AccessToken.getAdditionalInformation();
        //        ////取出用户身份信息
        //        //String principal = MapUtils.getString(additionalInformation, "user_name");
        //        ////获取用户权限
        //        //List<String> authorities = (List<String>) additionalInformation.get("authorities");
        //        //
        //        //Map<String,Object> info=new HashMap<>();
        //        //info.put("principal",principal);
        //        //info.put("authorities",authorities);
        //        ////给header里面添加值
        //        //String base64 = EncryptTool.encodeUTF8StringBase64(JSONTool.toJSONString(info));
        //        //ServerHttpRequest tokenRequest = exchange.getRequest().mutate().header("json-token", base64).build();
        //        //ServerWebExchange build = exchange.mutate().request(tokenRequest).build();
        //        //return chain.filter(build);
        //
        //    }
        //
        //    return chain.filter(exchange);
        //
        //} catch (InvalidTokenException e) {
        //    // log.info("无效的token: {}", token);
        //    return invalidTokenHandle(exchange);
        //}
        //catch (Exception ex)
        //{
        //    return invalidTokenHandle(exchange);
        //}

//        if (HttpMethod.POST.equals(exchange.getRequest().getMethod()) && null != exchange.getRequest().getHeaders().getContentType()
//                && exchange.getRequest().getHeaders().getContentType().includes(MediaType.APPLICATION_JSON)
//                && !exchange.getRequest().getHeaders().getContentType().includes(MediaType.MULTIPART_FORM_DATA))
        {
            return chain.filter(exchange.mutate()
                    .request(new ServerHttpRequestDecorator(request) {
                        //请求体不全问题处理，Post请求参数只能读取一次，需要重新构造一个request来替换原先的request
                        //2022-12-7，实测在之前从request中获取参数，在此使用exchange.mutate()后不影响api获取body和header和body中的参数
                        @Override

                        public Flux<DataBuffer> getBody() {
                            //在此处无法使用requestHeaderContentType，得到的对象为null
                            //System.out.println("request header Content-Type getType=" + requestHeaderContentType.getType() + " getSubtype=" + requestHeaderContentType.getSubtype() + " getSubtypeSuffix=" + requestHeaderContentType.getSubtypeSuffix());
                            //测试：不对request进行任何处理，直接返回request
                            //return request.getBody();
                            //获取request信息测试，根据不同的request中的header进行不同返回处理，默认为json
                            MediaType requestHeaderContentType = MediaType.APPLICATION_JSON;
                            if (request != null && request.getHeaders() != null && request.getHeaders().size() > 0x0 && request.getHeaders().getContentType() != null) {
                                //获取请求中的ContentType
                                requestHeaderContentType = request.getHeaders().getContentType();
                            }
                            if (API_LOG_LEVEL == ApiLogLevelEnum.DEV)
                                System.out.println("request header Content-Type getType=" + requestHeaderContentType);
                            //获取request中的body，json化后记录在请求-响应日志中
                            //2023-6-4，增加判断Content-Type:"multipart/form-data; 表示为上传文件时不对request的body进行记录
                            //实测requestHeader为：Content-Type:"multipart/form-data; boundary=--------------------------596918611915307255524812"
                            //System.out.println("request header Content-Type getType=" + requestHeaderContentType.getType() + " getSubtype=" + requestHeaderContentType.getSubtype() + " getSubtypeSuffix=" + requestHeaderContentType.getSubtypeSuffix());
                            //调试输出结果为：request header Content-Type getType=multipart getSubtype=form-data getSubtypeSuffix=null
                            if (requestHeaderContentType.getType().equalsIgnoreCase(MediaType.MULTIPART_FORM_DATA.getType())) {
                                if (API_LOG_LEVEL == ApiLogLevelEnum.DEV) {
                                    System.out.println("request header Content-Type 为 multipart，不对请求的body进行json化日志记录");
                                }
                                //如果Content-Type为multipart，不获取请求body进行json化后记录到日志中    
                                return request.getBody();
                            } else {
                                //此处的编码可以从header中获取
                                Flux<DataBuffer> dataBufferFlux = request.getBody()
                                        //使用 buffer 在这里将所有 body 读取完避免拆包影响
                                        .buffer()
                                        .map(body -> {
                                            //将所有 buffer 粘合在一起
                                            DataBuffer dataBuffers = dataBufferFactory.join(body);
                                            int dataBuffersCount = dataBuffers.readableByteCount();
                                            byte[] requesyBodyBytes = new byte[dataBuffersCount];
                                            dataBuffers.read(requesyBodyBytes);
                                            // DataBuffer 需要手动释放，如果不手动释放那么底层的计数一直不归零会造成内存泄漏
                                            DataBufferUtils.release(dataBuffers);

                                            //设置api日志中的body属性
                                            //此处的编码可以从header中获取
                                            apiLog.setRequestBodyJson(new String(requesyBodyBytes, QCConstant.CHARSET_UTF8));

                                            return dataBufferFactory.wrap(requesyBodyBytes);
                                        });

                                return dataBufferFlux;
                            }
                        }
                    })
                    .response(new ServerHttpResponseDecorator(response) {
                        DataBufferFactory bufferFactory = response.bufferFactory();

                        @Override
                        public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
                            //获取response中的状态码
                            HttpStatus statusCode = response.getStatusCode();
                            long contentLength = response.getHeaders().getContentLength();
                            MediaType responseMediaType = response.getHeaders().getContentType();

                            apiLog.setResponseHttpStatus(statusCode);
                            apiLog.setResponseContentLength(contentLength);
                            apiLog.setResponseMediaType(responseMediaType);

                            if (API_LOG_LEVEL == ApiLogLevelEnum.DEV) {
                                System.out.println("-----------------Gateway全局拦截器-----------------responseDecorator writeWith-----------------");
                                System.out.println("response statusCode=" + statusCode + " getContentLength=" + contentLength + " responseMediaType=" + responseMediaType + " body instanceof Mono=" + (body instanceof Mono) + " body instanceof Flux=" + (body instanceof Flux));
                                System.out.println("response getHeaders=" + response.getHeaders());
                            }
                            //2023-7-17，实测调用api接口返回图片得到response中header的Content-Type:"image/jpeg"；如果返回的是图片或文件不进行包装处理
                            //由于在后面获取response内容进行处理和记录时均是以json对象为基础，在此判断业务接口返回的不是json时不进行封装处理，直接返回业务接口数据
                            //比如业务接口返回的是image、pdf等，不进行处理，直接返回原始response
                            //判断是否需要进行统一封装处理，对于请求返回图片、文件等不需要封装，直接返回业务接口的response结果
                            boolean isReturnRawResponse = false;
                            if (responseMediaType != null
                                    && !responseMediaType.isCompatibleWith(MediaType.APPLICATION_JSON)
                                    && !responseMediaType.isCompatibleWith(MediaType.APPLICATION_XML)
                                    && !responseMediaType.isCompatibleWith(MediaType.TEXT_PLAIN)) {
                                isReturnRawResponse = true;
                            }
                            if (isReturnRawResponse) {
                                //返回业务接口原始的response，不需要进行封装处理
                                //请求--响应成功，响应结果为图片或文件等类型，需要直接返回响应原始信息；在此记录日志，并返回response
                                apiLog.setEndTime(System.currentTimeMillis());
                                writeApiLog("writeWith Raw Response", apiLog);

                                //return super.writeWith(DataBufferUtils.join(Flux.from(body)));
                                return super.writeWith(Flux.from(body));
                            }

                            //实测发现api返回null时response中的结果为空，不会进到writeWith的map方法中
                            //判断response.getHeaders().getContentLength()，如果长度为0，不会进到writeWith的map方法中，在此进行封装处理返回response
                            //在api中返回结果类型为String时，返回null表示成功，此时获取response.getHeaders().getContentLength()为0
                            //在api中返回结果类型不为String，返回null表示无数据，此时获取response.getHeaders().getContentLength()为-1
                            //在此判断response.getHeaders().getContentLength()为0时，判定为api返回结果为String的成功处理，在此进行封装后返回；其他情况进入writeWith方法进行处理
                            //在此判断如果状态码为OK，返回信息长度=0时认为api返回的为null，在此返回成功封装信息
                            if (statusCode == HttpStatus.OK && response.getHeaders().getContentLength() == 0x0) {
                                //请求--响应成功，响应状态为OK、响应长度为0;表示接口返回null，在此进行渠成网关的统一封装处理；在此记录日志，并返回封装的response
                                apiLog.setEndTime(System.currentTimeMillis());

                                String wrapperResponseData = JsonUtil.toJSONString(Response.ok());
                                byte[] wrapperResponseBytes = wrapperResponseData.getBytes(QCConstant.CHARSET_UTF8);
                                this.getDelegate().getHeaders().setContentLength(wrapperResponseBytes.length);

                                apiLog.setResponseContentLength(wrapperResponseBytes.length);
                                apiLog.setResponseResultJson(wrapperResponseData);

                                writeApiLog("writeWith OK length=0", apiLog);

                                return this.getDelegate().writeWith(Flux.just(bufferFactory.wrap(wrapperResponseBytes)));
                            }

                            return super.writeWith(DataBufferUtils.join(Flux.from(body))
                                    .map(dataBuffers -> {
                                        //请求--响应成功，响应状态为OK、响应body中有内容;表示含义？？？
                                        // 获取响应体
                                        int dataBuffersCount = dataBuffers.readableByteCount();
                                        byte[] content = new byte[dataBuffersCount];
                                        dataBuffers.read(content);
                                        // DataBuffer 需要手动释放，如果不手动释放那么底层的计数一直不归零会造成内存泄漏
                                        DataBufferUtils.release(dataBuffers);

                                        return content;
                                    })
                                    .flatMap(bytes -> {
                                        //判断api中是否有提示信息
                                        boolean isApiReturnPromptInfo = false;
                                        // 将响应体转换为String
                                        //String rawResponseData = new String(uncompress(content), CHARSET_UTF8);
                                        String rawResponseData = new String(bytes, QCConstant.CHARSET_UTF8);
                                        //由于api中返回的信息无法判定为成功还是失败，需要在此处理api中返回值为string情况下null表示成功，非null表示提示信息的情况
                                        //无法根据得到的rawResponseData的byte数组或字符串进行判断，api中返回值为string的禁止返回除了null以外的其他字符串
                                        //通过编码约定进行处理，需要使用string作为返回类型，如表示提示信息以QCConstant.APP_PROMPT_PREFIX前缀开头
                                        if (rawResponseData.length() > QCConstant.APP_PROMPT_PREFIX.length() && rawResponseData.indexOf(QCConstant.APP_PROMPT_PREFIX) == 0) {
                                            isApiReturnPromptInfo = true;
                                            rawResponseData = rawResponseData.substring(QCConstant.APP_PROMPT_PREFIX.length());
                                        }
                                        apiLog.setResponseRawData(rawResponseData);
                                        //直接将api响应数据字符串返回会以字符串类型返回，导致前后加双引号，并且中间的键值对上都会加上双引号转义字符，不是正确的json格式
                                        //api中返回的对象需要区分为Object还是Array，需要使用不同的JSON解析方法
                                        boolean apiResultIsArray = false;
                                        Object apiResultObj = null;
                                        //将rawResponseData尝试转换为Json对象，如果转换失败判定为rawResponseData为字符串（非Json对象）
                                        try {
                                            Object convertJsonObject = JSON.parse(rawResponseData);
                                            if (convertJsonObject instanceof JSONObject) {
                                                apiResultIsArray = false;
                                                apiResultObj = (JSONObject) convertJsonObject;
                                            } else if (convertJsonObject instanceof JSONArray) {
                                                apiResultIsArray = true;
                                                apiResultObj = (JSONArray) convertJsonObject;
                                            } else {
                                                if (API_LOG_LEVEL == ApiLogLevelEnum.DEV)
                                                    System.out.println("*********************rawResponseData不是JSONObject或JSONArray*********************");
                                            }
                                        } catch (Exception e) {
                                            if (API_LOG_LEVEL == ApiLogLevelEnum.DEV)
                                                System.out.println("*********************将rawResponseData尝试转换为Json对象失败，rawResponseData可能为字符串或简单数据类型*********************");

                                            //如果使用JSONObject和JSONArray均转换失败，返回的原始返回结果可能为字符串，直接进行赋值
                                            apiResultObj = rawResponseData;

                                            e.printStackTrace();
                                        }

                                        // 进行业务处理，最后得到返回的响应结果
                                        String wrapperResponseData = null;

                                        // 对响应状态码和api返回结果进行业务判断
                                        if (statusCode == HttpStatus.OK) {
                                            //isApiReturnPromptInfo判定是通过rawResponseData还是是否使用APP_PROMPT_PREFIX作为开头，即rawResponseData还是为String类型，此时apiResultObj与rawResponseData完全相同
                                            //由于Response.warn方法第2个参数类型为String，表示错误或提示信息，因此Response.warn中参数使用Response.warn；
                                            //Response.ok方法参数使用apiResultObj或rawResponseData最终得到的结果完全相同
                                            if (isApiReturnPromptInfo)
                                                wrapperResponseData = JsonUtil.toJSONString(Response.warn(null, rawResponseData));
                                            else {
                                                wrapperResponseData = JsonUtil.toJSONString(Response.ok(apiResultObj));
                                            }
                                        } else if (statusCode == HttpStatus.NOT_FOUND) {
                                            //2022-12-6，实际测试结果
                                            //在路由地址不匹配时api返回状态码为404，得到的数据如下：
                                            //{\"timestamp\":\"2022-12-06T02:41:49.180+00:00\",\"status\":404,\"error\":\"Not Found\",\"message\":\"No message available\",\"path\":\"/qzj/test\"}
                                            wrapperResponseData = JsonUtil.toJSONString(Response.error("请求地址或参数不正确"));
                                        } else if (statusCode == HttpStatus.INTERNAL_SERVER_ERROR) {
                                            //在api的配置文件中未配置异常显示时，返回数据如下：
                                            //{"timestamp":"2022-12-12T05:55:01.758+00:00","status":500,"error":"Internal Server Error","path":"/qzj/query"}
                                            //在api的配置文件中配置异常显示后，返回数据如下：
                                            //{"timestamp":"2022-12-12T06:00:59.018+00:00","status":500,"error":"Internal Server Error","exception":"qc.common.core.exception.QCPromptException","message":"起始时间不能大于截止时间","path":"/qzj/query"}
                                            //在此获取api中返回的数据，其中如能解析出异常可以获取其中的信息进行处理，有预期类的异常表示输入参数错误或提示提示信息（并非异常）
                                            boolean isQCInputException = false;
                                            try {
                                                //在之前已将字符串解析为JSON对象
                                                if (apiResultObj != null) {
                                                    //如果api中的结果类型为Object判断其中是否包含异常信息，如果为Array就没有异常信息，将所有返回内容均返回
                                                    if (apiResultIsArray) {
                                                        wrapperResponseData = JsonUtil.toJSONString(Response.warn(null, rawResponseData));
                                                    } else {
                                                        JSONObject apiResultObject = (JSONObject) apiResultObj;
                                                        //获取api返回json结果中的exception内容
                                                        String exceptionString = apiResultObject.getString("exception");

                                                        if (!StringUtils.isBlank(exceptionString)) {
                                                            //判断exception内容为抛出的提示异常类型
                                                            if (StringUtils.endsWithIgnoreCase(exceptionString, "QCPromptException")) {
                                                                isQCInputException = true;
                                                                //获取提示信息内容
                                                                String messageString = apiResultObject.getString("message");
                                                                if (!StringUtils.isBlank(messageString)) {
                                                                    //返回提示信息内容
                                                                    wrapperResponseData = JsonUtil.toJSONString(Response.warn(null, messageString));
                                                                } else {
                                                                    //获取不到异常message，将api返回内容全部返回
                                                                    wrapperResponseData = JsonUtil.toJSONString(Response.warn(null, rawResponseData));
                                                                }
                                                            }
                                                        }
                                                    }
                                                }
                                            } catch (Exception ex) {
                                                ex.printStackTrace();
                                            }

                                            if (!isQCInputException)//不是预期内的异常，返回异常结果
                                                wrapperResponseData = JsonUtil.toJSONString(Response.error(rawResponseData));
                                        } else {
                                            //其他状态码，默认返回为异常结果
                                            //无服务可用 if (statusCode == HttpStatus.SERVICE_UNAVAILABLE)
                                            wrapperResponseData = JsonUtil.toJSONString(Response.error(statusCode.toString()));
                                        }

                                        apiLog.setResponseResultJson(wrapperResponseData);
                                        apiLog.setEndTime(System.currentTimeMillis());

                                        //设置最终返回响应的信息
                                        //经过wrapper处理后将response的状态码设置为ok
                                        response.setStatusCode(HttpStatus.OK);
                                        //在本身api返回OK情况下会出现请求响应不终止的情况，测试分析可能与返回的内容长度与实际内容不一致，实测确定是该原因，在此设置最终的长度
                                        byte[] wrapperResponseBytes = wrapperResponseData.getBytes(QCConstant.CHARSET_UTF8);
                                        response.getHeaders().setContentLength(wrapperResponseBytes.length);

                                        //请求--响应完成，响应的状态码包含200、400、500等，在此分别进行判断处理；在此记录日志，并返回封装的response
                                        writeApiLog("writeWith flatMap", apiLog);

                                        return Mono.just(bufferFactory.wrap(wrapperResponseBytes));
                                    }));
                        }

                        @Override
                        public Mono<Void> writeAndFlushWith(Publisher<? extends Publisher<? extends DataBuffer>> body) {
                            return writeWith(Flux.from(body).flatMapSequential(p -> p));
                        }
                    })
                    .build());
        }
    }

    /***
     * 获取API接口访问权限控制所需数据，包含：API、APP、AppKey、部门、菜单、角色、部门与用户关联、菜单与API关联、角色关联部门、角色关联菜单、角色关联用户
     *
     * @return void
     * @author QuCheng Tech
     * @since 2024/4/25
     */
    void GetApiAuthorityDatas() {
        //this.ApiAuthorityApis = apiAuthorityService.getAllQcApis();
        //this.ApiAuthorityApps = apiAuthorityService.getAllQcApps();
        //this.ApiAuthorityAppKeys = apiAuthorityService.getAllQcAppKeys();
        this.ApiAuthorityDepts = apiAuthorityService.getAllQcDepts();
        this.ApiAuthorityDeptUsers = apiAuthorityService.getAllQcDeptUsers();
        this.ApiAuthorityMenus = apiAuthorityService.getAllQcMenus();
        //this.ApiAuthorityMenuApis = apiAuthorityService.getAllQcMenuApis();
        this.ApiAuthorityRoles = apiAuthorityService.getAllQcRoles();
        this.ApiAuthorityRoleUsers = apiAuthorityService.getAllQcRoleUsers();
        this.ApiAuthorityRoleDepts = apiAuthorityService.getAllQcRoleDepts();
        this.ApiAuthorityRoleMenus = apiAuthorityService.getAllQcRoleMenus();
        //this.ApiAuthorityRoleApis = apiAuthorityService.getAllQcRoleApis();
    }

    /***
     * 写入API访问日志，在此方法中根据过滤规则和响应结果判断是否需要进行日志记录
     *
     * @param positionName 标识调用写日志方法的位置
     * @param log 日志信息
     * @return void
     * @author QuCheng Tech
     * @since 2023/11/7
     */
    void writeApiLog(String positionName, QcApiLogDto log) {
        String userAgentString = log.getRequestHeaderUserAgent().toString();
        String remoteAddressString = log.getRequestRemoteAddress().toString();
        String queryParamString = null;
        if (log.getRequestQueryParams() != null && log.getRequestQueryParams().size() > 0x0)
            queryParamString = JsonUtil.toJSONString(log.getRequestQueryParams());
        Long timespan = log.getEndTime() - log.getStartTime();
        //如果是DEV级别，调试状态下输出request和response信息
        if (API_LOG_LEVEL == ApiLogLevelEnum.DEV) {
            System.out.println("-----------------Gateway全局拦截器-----------------");
            System.out.println("-→-→-→position  " + positionName);
            System.out.println("-→-→-→request-----------------");
            System.out.println("-------→-→-→-→method  " + log.getRequestMethod());
            System.out.println("-------→-→-→-→uri  " + log.getRequestUri());
            System.out.println("-------→-→-→-→RequestHeaderContentType  " + log.getRequestHeaderContentType());
            System.out.println("-------→-→-→-→RequestHeaderUserAgent  " + userAgentString);
            System.out.println("-------→-→-→-→RequestRemoteAddress  " + remoteAddressString);
            System.out.println("-------→-→-→-→url  " + log.getRequestUrl());
            System.out.println("-------→-→-→-→QueryParams  " + queryParamString);
            System.out.println("-------→-→-→-→request body-----------------");
            System.out.println(log.getRequestBodyJson());

            System.out.println("-→-→-→response-----------------");
            System.out.println("-------→-→-→-→status  " + log.getResponseHttpStatus());
            System.out.println("-------→-→-→-→ResponseMediaType  " + log.getResponseMediaType());
            System.out.println("-------→-→-→-→ResponseContentLength  " + log.getResponseContentLength());
            System.out.println("-------→-→-→-→StartTime  " + log.getStartTime());
            System.out.println("-------→-→-→-→EndTime  " + log.getEndTime());
            System.out.println("-------→-→-→-→ts  " + timespan);
            System.out.println("-------→-→-→-→response body Raw Data-----------------");
            //如果响应数据超过设置的记录日志最大长度，不进行显示
            if (log.getResponseRawData() != null && log.getResponseRawData().length() > WRITE_LOG_MAX_DATA_LENGTH)
                System.out.println("response body Raw Data 长度超过：" + WRITE_LOG_MAX_DATA_LENGTH + " 长度太长不进行显示");
            else
                System.out.println(log.getResponseRawData());
            System.out.println("-------→-→-→-→response body Wrapper Data-----------------");
            if (log.getResponseResultJson() != null && log.getResponseResultJson().length() > WRITE_LOG_MAX_DATA_LENGTH)
                System.out.println("response body Wrapper Data 长度超过：" + WRITE_LOG_MAX_DATA_LENGTH + " 长度太长不进行显示");
            else
                System.out.println(log.getResponseResultJson());
        }

        //根据日志级别和过滤规则判断是否需要记录请求响应基本信息和是否需要记录body信息
        boolean needWriteApiLog = false;
        boolean needWriteBody = false;
        if (API_LOG_LEVEL == ApiLogLevelEnum.DEV) {
            //dev级别下所有访问都需要进行详细记录
            needWriteApiLog = true;
            needWriteBody = true;
        } else if (API_LOG_LEVEL == ApiLogLevelEnum.RUN) {
            //run级别下记录所有请求，不包含数据
            needWriteApiLog = true;
            needWriteBody = false;
        } else {
            //stable和off级别下不需要记录请求
            needWriteApiLog = false;
            needWriteBody = false;
        }
        //在RUN和STABLE级别时判断请求是否正常，不正常的需要记录body
        if (API_LOG_LEVEL == ApiLogLevelEnum.RUN || API_LOG_LEVEL == ApiLogLevelEnum.STABLE) {
            if (log.getResponseHttpStatus() != HttpStatus.OK) {
                needWriteApiLog = true;
                needWriteBody = true;
            }
        }
        //如果相应数据超过设置的记录日志最大长度，不进行显示
        if (needWriteBody) {
            if (log.getResponseRawData() != null && log.getResponseRawData().length() > WRITE_LOG_MAX_DATA_LENGTH)
                needWriteBody = false;
        }
        //判断日志过滤规则是否匹配，最终是要得到是否记录body；如果是否记录body已经为true不需要在规则中进行匹配判断处理
        if (!needWriteBody) {
            if (API_LOG_LEVEL == ApiLogLevelEnum.RUN || API_LOG_LEVEL == ApiLogLevelEnum.STABLE) {
                Date now = DateUtil.getNowDate();
                //判断是否需要获取过滤规则
                if (latestGetFiltersDate == null || DateUtil.getDistanceMinutes(latestGetFiltersDate, now) >= REFRESH_FILTERS_MINUTES) {
                    //如果没有获取过滤规则或者上次获取的时间距当前时间大于获取间隔的分钟数，进行过滤规则获取
                    try {
                        //获取成功，对变量进行更新；不管本次获取到的数据是否为空
                        apiFilters = qcApiFilterService.getAllValidFilters();
                        latestGetFiltersDate = now;
                    } catch (Exception e) {
                        //获取规则错误、失败时不进行处理，也不对原有过滤规则进行更新
                        e.printStackTrace();
                    }
                }
                //有过滤规则时才进行匹配，没有过滤规则不需要进行匹配
                if (apiFilters != null && apiFilters.size() > 0x0) {
                    //匹配规则逻辑：先根据url匹配，再判断请求方式、耗时和用户
                    List<QcApiFilters> filterByUrl = apiFilters.stream().filter(p -> StringUtils.containsIgnoreCase(log.getRequestUrl(), p.getUrl())).collect(Collectors.toList());
                    if (filterByUrl != null && filterByUrl.size() > 0x0) {
                        for (QcApiFilters rule : filterByUrl) {
                            //如果过滤规则中有配置用户ID，本次请求的用户ID不匹配规则中的用户ID
                            if (rule.getUid() != null && rule.getUid() > 0x0) {
                                if (log.getUserId() != null && log.getUserId() > 0x0 && rule.getUid() != log.getUserId())
                                    continue;
                            }
                            //判断过滤规则中的耗时长度，如果有配置耗时但本次请求耗时小时过滤规则中的耗时
                            if (rule.getTs() != null && rule.getTs() > 0x0 && timespan.intValue() < rule.getCount())
                                continue;
                            //判断过滤规则中的请求方式不为空、也不为*表示所有，过滤规则中的请求方式与本次请求方式不同
                            if (StringUtils.isNotBlank(rule.getMethod()) && !StringUtils.equals(rule.getMethod(), "*") && !StringUtils.equalsIgnoreCase(rule.getMethod(), log.requestMethod.name()))
                                continue;

                            //过滤规则不匹配的情况已经在上面使用continue跳出，执行到此表示匹配当前规则
                            //更新过滤规则的命中次数
                            try {
                                qcApiFilterService.addHitCounts(rule.getId());
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                            //设置需要记录日志和记录body信息为true
                            needWriteApiLog = true;
                            needWriteBody = true;
                            //退出循环
                            break;
                        }
                    }
                }
            }
        }

        if (needWriteApiLog) {
            //需要记录API日志，再判断是否需要记录body信息
            try {
                String addLogResult = qcApiLogService.add(log.getRequestId(), new Date(log.getStartTime()), log.getRequestMethod().name(), log.getRequestUrl(),
                        userAgentString, needWriteBody ? queryParamString : null, needWriteBody ? log.getRequestBodyJson() : null, log.getToken(),
                        log.getResponseHttpStatus().value(), needWriteBody ? log.getResponseRawData() : null, timespan.intValue(), needWriteBody ? log.getResponseResultJson() : null);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * gzip解密--暂未使用
     *
     * @param bytes 待解码数据
     * @return byte[] 解码结果
     * @author QuCheng Tech
     * @since 2023/6/4
     */
    public static byte[] uncompress(byte[] bytes) {
        if (bytes == null || bytes.length == 0) {
            return null;
        }
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        ByteArrayInputStream in = new ByteArrayInputStream(bytes);
        try {
            GZIPInputStream ungzip = new GZIPInputStream(in);
            byte[] buffer = new byte[256];
            int n;
            while ((n = ungzip.read(buffer)) >= 0) {
                out.write(buffer, 0, n);
            }
        } catch (IOException e) {
            System.out.println("gzip uncompress error.");
        }

        return out.toByteArray();
    }

    /**
     * gzip编码--暂未使用
     *
     * @param str      编码内容
     * @param encoding 编码字符集
     * @return byte[] 编码后的数据
     * @author QuCheng Tech
     * @since 2023/6/4
     */
    public static byte[] compress(String str, String encoding) {
        if (str == null || str.length() == 0) {
            return null;
        }
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        GZIPOutputStream gzip;
        try {
            gzip = new GZIPOutputStream(out);
            gzip.write(str.getBytes(encoding));
            gzip.close();
        } catch (IOException e) {
            System.out.println("gzip compress error.");
        }
        return out.toByteArray();

    }

    /**
     * 信息脱敏处理，用于对手机号等信息进行处理，暂时不使用
     *
     * @param responseData, filterField
     * @return java.lang.String
     * @author QuCheng Tech
     * @date 2022/12/6
     */
    public String dataHandling(String responseData, List<String> filterField) {
        //需要使用时参考此文章
        //Spring Cloud 网关(GlobalFilter)拦截响应体
        //https://cloud.tencent.com/developer/article/1893101?from=15425
        return responseData;
    }

    /***
     * 根据指定的响应状态枚举进行返回响应信息
     *
     * @param exchange 请求和响应对象
     * @param responseStatus 响应状态枚举
     * @param msg 返回的信息文字，可以为null，为null时返回的文字为相应状态枚举的名称
     * @param apiLog api日志信息
     * @return reactor.core.publisher.Mono<java.lang.Void>
     * @author QuCheng Tech
     * @since 2024/4/23
     */
    private Mono<Void> responseByStatus(ServerWebExchange exchange, ResponseStatusEnum responseStatus, String msg, QcApiLogDto apiLog) {
        //根据响应的状态枚举对apiLog中的结果进行赋值
        //将响应状态码设置为401--UNAUTHORIZED--未经授权
        apiLog.setResponseHttpStatus(HttpStatus.UNAUTHORIZED);
        //设置api日志中的响应结束时间为当前时间，方便在writeApiLog中计算耗时
        apiLog.setEndTime(System.currentTimeMillis());

        //生成相应结果json字符串，在api日志中设置响应的长度和结果json
        String responseJsonString = JsonUtil.toJSONString(Response.ok(null, responseStatus.getValue(), msg == null ? responseStatus.getDesc() : msg));
        apiLog.setResponseContentLength(responseJsonString.length());
        apiLog.setResponseResultJson(responseJsonString);

        writeApiLog("responseByStatus", apiLog);

        return responseResultBuild(responseJsonString, exchange);
    }

    /**
     * 统一返回处理
     *
     * @param jsonString json字符串
     * @param exchange   请求和响应对象
     * @return
     */
    private Mono<Void> responseResultBuild(String jsonString, ServerWebExchange exchange) {
        ServerHttpResponse response = exchange.getResponse();
        byte[] bits = jsonString.getBytes(StandardCharsets.UTF_8);
        DataBuffer buffer = response.bufferFactory().wrap(bits);
        response.setStatusCode(HttpStatus.OK);
        response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        return response.writeWith(Mono.just(buffer));
    }

    @Override
    public int getOrder() {
        //值越小优先级越高，实测设置为0时不会加载到responseDecorator
        // -1 is response write filter, must be called before that
        //指定顺序，在 CommonTraceFilter（这个Filter是读取链路信息，最好在所有 Filter 之前） 之后
        //return 0x0;
        return -2;
    }
}
